package com.plamwind;

import java.io.*;
import java.net.*;
import java.util.*;

import com.palmwindActivity.proto.ProtoBuf;
import com.palmwindActivity.proto.ProtoBufUtil;
import com.palmwindActivity.proto.TextUpload;

/**
 * This class is a generic framework for a flexible, multi-threaded server. It
 * listens on any number of specified ports, and, when it receives a connection
 * on a port, passes input and output streams to a specified Service object
 * which provides the actual service. It can limit the number of concurrent
 * connections, and logs activity to a specified stream.
 **/
public class Server {
	/**
	 * A main() method for running the server as a standalone program. The
	 * command-line arguments to the program should be pairs of servicenames and
	 * port numbers. For each pair, the program will dynamically load the named
	 * Service class, instantiate it, and tell the server to provide that
	 * Service on the specified port. The special -control argument should be
	 * followed by a password and port, and will start special server control
	 * service running on the specified port, protected by the specified
	 * password.
	 **/
	public static void main(String[] args) {
		try {
			if (args.length < 2) // Check number of arguments
				throw new IllegalArgumentException("Must specify a service");

			// Create a Server object that uses standard out as its log and
			// has a limit of ten concurrent connections at once.
			Server s = new Server(System.out, 10);

			// Parse the argument list
			int i = 0;
			while (i < args.length) {
				if (args[i].equals("-control")) { // Handle the -control arg
					i++;
					String password = args[i++];
					int port = Integer.parseInt(args[i++]);
					// add control service
					s.addService(new Control(s, password), port);
				} else {
					// Otherwise start a named service on the specified port.
					// Dynamically load and instantiate a Service class
					String serviceName = args[i++];
					Class serviceClass = Class.forName(serviceName);
					Service service = (Service) serviceClass.newInstance();
					int port = Integer.parseInt(args[i++]);
					s.addService(service, port);
				}
			}
		} catch (Exception e) { // Display a message if anything goes wrong
			System.err.println("Server: " + e);
			System.err.println("Usage: java Server " + "[-control  ] "
					+ "[  ... ]");
			System.exit(1);
		}
	}

	// This is the state for the server
	Map services; // Hashtable mapping ports to Listeners
	Set connections; // The set of current connections
	int maxConnections; // The concurrent connection limit
	ThreadGroup threadGroup; // The threadgroup for all our threads
	PrintWriter logStream; // Where we send our logging output to

	/**
	 * This is the Server() constructor. It must be passed a stream to send log
	 * output to (may be null), and the limit on the number of concurrent
	 * connections.
	 **/
	public Server(OutputStream logStream, int maxConnections) {
		setLogStream(logStream);
		log("Starting server");
		threadGroup = new ThreadGroup(Server.class.getName());
		this.maxConnections = maxConnections;
		services = new HashMap();
		connections = new HashSet(maxConnections);
	}

	/**
	 * A public method to set the current logging stream. Pass null to turn
	 * logging off
	 **/
	public synchronized void setLogStream(OutputStream out) {
		if (out != null)
			logStream = new PrintWriter(out);
		else
			logStream = null;
	}

	/** Write the specified string to the log */
	protected synchronized void log(String s) {
		if (logStream != null) {
			logStream.println("[" + new Date() + "] " + s);
			logStream.flush();
		}
	}

	/** Write the specified object to the log */
	protected void log(Object o) {
		log(o.toString());
	}

	/**
	 * This method makes the server start providing a new service. It runs the
	 * specified Service object on the specified port.
	 **/
	public synchronized void addService(Service service, int port)
			throws IOException {
		Integer key = new Integer(port); // the hashtable key
		// Check whether a service is already on that port
		if (services.get(key) != null)
			throw new IllegalArgumentException("Port " + port
					+ " already in use.");
		// Create a Listener object to listen for connections on the port
		Listener listener = new Listener(threadGroup, port, service);
		// Store it in the hashtable
		services.put(key, listener);
		// Log it
		log("Starting service " + service.getClass().getName() + " on port "
				+ port);
		// Start the listener running.
		listener.start();
	}

	/**
	 * This method makes the server stop providing a service on a port. It does
	 * not terminate any pending connections to that service, merely causes the
	 * server to stop accepting new connections
	 **/
	public synchronized void removeService(int port) {
		Integer key = new Integer(port); // hashtable key
		// Look up the Listener object for the port in the hashtable
		final Listener listener = (Listener) services.get(key);
		if (listener == null)
			return;
		// Ask the listener to stop
		listener.pleaseStop();
		// Remove it from the hashtable
		services.remove(key);
		// And log it.
		log("Stopping service " + listener.service.getClass().getName()
				+ " on port " + port);
	}

	/**
	 * This nested Thread subclass is a "listener". It listens for connections
	 * on a specified port (using a ServerSocket) and when it gets a connection
	 * request, it calls the servers addConnection() method to accept (or
	 * reject) the connection. There is one Listener for each Service being
	 * provided by the Server.
	 **/
	public class Listener extends Thread {
		ServerSocket listen_socket; // The socket to listen for connections
		int port; // The port we're listening on
		Service service; // The service to provide on that port
		volatile boolean stop = false; // Whether we've been asked to stop

		/**
		 * The Listener constructor creates a thread for itself in the
		 * threadgroup. It creates a ServerSocket to listen for connections on
		 * the specified port. It arranges for the ServerSocket to be
		 * interruptible, so that services can be removed from the server.
		 **/
		public Listener(ThreadGroup group, int port, Service service)
				throws IOException {
			super(group, "Listener:" + port);
			listen_socket = new ServerSocket(port);
			// give it a non-zero timeout so accept() can be interrupted
			listen_socket.setSoTimeout(600000);
			this.port = port;
			this.service = service;
		}

		/**
		 * This is the polite way to get a Listener to stop accepting
		 * connections
		 ***/
		public void pleaseStop() {
			this.stop = true; // Set the stop flag
			this.interrupt(); // Stop blocking in accept()
			try {
				listen_socket.close();
			} // Stop listening.
			catch (IOException e) {
			}
		}

		/**
		 * A Listener is a Thread, and this is its body. Wait for connection
		 * requests, accept them, and pass the socket on to the addConnection
		 * method of the server.
		 **/
		public void run() {
			while (!stop) { // loop until we're asked to stop.
				try {
					Socket client = listen_socket.accept();
					addConnection(client, service);
				} catch (InterruptedIOException e) {
				} catch (IOException e) {
					log(e);
				}
			}
		}
	}

	/**
	 * This is the method that Listener objects call when they accept a
	 * connection from a client. It either creates a Connection object for the
	 * connection and adds it to the list of current connections, or, if the
	 * limit on connections has been reached, it closes the connection.
	 **/
	protected synchronized void addConnection(Socket s, Service service) {
		// If the connection limit has been reached
		if (connections.size() >= maxConnections) {
			try {
				// Then tell the client it is being rejected.
				PrintWriter out = new PrintWriter(s.getOutputStream());
				out.print("Connection refused; "
						+ "the server is busy; please try again later.\n");
				out.flush();
				// And close the connection to the rejected client.
				s.close();
				// And log it, of course
				log("Connection refused to "
						+ s.getInetAddress().getHostAddress() + ":"
						+ s.getPort() + ": max connections reached.");
			} catch (IOException e) {
				log(e);
			}
		} else { // Otherwise, if the limit has not been reached
			// Create a Connection thread to handle this connection
			Connection c = new Connection(s, service);
			// Add it to the list of current connections
			connections.add(c);
			// Log this new connection
			log("Connected to " + s.getInetAddress().getHostAddress() + ":"
					+ s.getPort() + " on port " + s.getLocalPort()
					+ " for service " + service.getClass().getName());
			// And start the Connection thread to provide the service
			c.start();
		}
	}

	/**
	 * A Connection thread calls this method just before it exits. It removes
	 * the specified Connection from the set of connections.
	 **/
	protected synchronized void endConnection(Connection c) {
		connections.remove(c);
		log("Connection to " + c.client.getInetAddress().getHostAddress() + ":"
				+ c.client.getPort() + " closed.");
	}

	/** Change the current connection limit */
	public synchronized void setMaxConnections(int max) {
		maxConnections = max;
	}

	/**
	 * This method displays status information about the server on the specified
	 * stream. It can be used for debugging, and is used by the Control service
	 * later in this example.
	 **/
	public synchronized void displayStatus(PrintWriter out) {
		// Display a list of all Services that are being provided
		Iterator keys = services.keySet().iterator();
		while (keys.hasNext()) {
			Integer port = (Integer) keys.next();
			Listener listener = (Listener) services.get(port);
			out.print("SERVICE " + listener.service.getClass().getName()
					+ " ON PORT " + port + "\n");
		}

		// Display the current connection limit
		out.print("MAX CONNECTIONS: " + maxConnections + "\n");

		// Display a list of all current connections
		Iterator conns = connections.iterator();
		while (conns.hasNext()) {
			Connection c = (Connection) conns.next();
			out.print("CONNECTED TO "
					+ c.client.getInetAddress().getHostAddress() + ":"
					+ c.client.getPort() + " ON PORT "
					+ c.client.getLocalPort() + " FOR SERVICE "
					+ c.service.getClass().getName() + "\n");
		}
	}

	/**
	 * This class is a subclass of Thread that handles an individual connection
	 * between a client and a Service provided by this server. Because each such
	 * connection has a thread of its own, each Service can have multiple
	 * connections pending at once. Despite all the other threads in use, this
	 * is the key feature that makes this a multi-threaded server
	 * implementation.
	 **/
	public class Connection extends Thread {
		Socket client; // The socket to talk to the client through
		Service service; // The service being provided to that client

		/**
		 * This constructor just saves some state and calls the superclass
		 * constructor to create a thread to handle the connection. Connection
		 * objects are created by Listener threads. These threads are part of
		 * the server's ThreadGroup, so all Connection threads are part of that
		 * group, too.
		 **/
		public Connection(Socket client, Service service) {
			super("Server.Connection:"
					+ client.getInetAddress().getHostAddress() + ":"
					+ client.getPort());
			this.client = client;
			this.service = service;
		}

		/**
		 * This is the body of each and every Connection thread. All it does is
		 * pass the client input and output streams to the serve() method of the
		 * specified Service object. That method is responsible for reading from
		 * and writing to those streams to provide the actual service. Recall
		 * that the Service object has been passed from the Server.addService()
		 * method to a Listener object to the addConnection() method to this
		 * Connection object, and is now finally being used to provide the
		 * service. Note that just before this thread exits it always calls the
		 * endConnection() method to remove itself from the set of connections
		 **/
		public void run() {
			try {
				InputStream in = client.getInputStream();
				OutputStream out = client.getOutputStream();
				service.serve(in, out);
			} catch (IOException e) {
				log(e);
			} finally {
				endConnection(this);
			}
		}
	}

	/**
	 * Here is the Service interface that we have seen so much of. It defines
	 * only a single method which is invoked to provide the service. serve()
	 * will be passed an input stream and an output stream to the client. It
	 * should do whatever it wants with them, and should close them before
	 * returning.
	 * 
	 * All connections through the same port to this service share a single
	 * Service object. Thus, any state local to an individual connection must be
	 * stored in local variables within the serve() method. State that should be
	 * global to all connections on the same port should be stored in instance
	 * variables of the Service class. If the same Service is running on more
	 * than one port, there will typically be different Service instances for
	 * each port. Data that should be global to all connections on any port
	 * should be stored in static variables.
	 * 
	 * Note that implementations of this interface must have a no-argument
	 * constructor if they are to be dynamically instantiated by the main()
	 * method of the Server class.
	 **/
	public interface Service {
		public void serve(InputStream in, OutputStream out) throws IOException;
	}

	/**
	 * A very simple service. It displays the current time on the server to the
	 * client, and closes the connection.
	 **/
	public static class Time implements Service {
		public void serve(InputStream i, OutputStream o) throws IOException {
			PrintWriter out = new PrintWriter(o);
			out.print(new Date() + "\n");
			out.close();
			i.close();
		}
	}

	/**
	 * This is another example service. It reads lines of input from the client,
	 * and sends them back, reversed. It also displays a welcome message and
	 * instructions, and closes the connection when the user enters a '.' on a
	 * line by itself.
	 **/
	public static class Reverse implements Service {
		public void serve(InputStream i, OutputStream o) throws IOException {
			BufferedReader in = new BufferedReader(new InputStreamReader(i));
			PrintWriter out = new PrintWriter(new BufferedWriter(
					new OutputStreamWriter(o)));
			out.print("Welcome to the line reversal server.\n");
			out.print("Enter lines.  End with a '.' on a line by itself.\n");
			for (;;) {
				out.print("> ");
				out.flush();
				String line = in.readLine();
				if ((line == null) || line.equals("."))
					break;
				for (int j = line.length() - 1; j >= 0; j--)
					out.print(line.charAt(j));
				out.print("\n");
			}
			out.close();
			in.close();
		}
	}

	/**
	 * This service is an HTTP mirror, just like the HttpMirror class
	 * implemented earlier in this chapter. It echos back the client's HTTP
	 * request
	 **/
	public static class HTTPMirror implements Service {
		public void serve(InputStream i, OutputStream o) throws IOException {
			BufferedReader in = new BufferedReader(new InputStreamReader(i));
			PrintWriter out = new PrintWriter(o);
			out.print("HTTP/1.0 200 \n");
			out.print("Content-Type: text/plain\n\n");
			String line;
			while ((line = in.readLine()) != null) {
				if (line.length() == 0)
					break;
				out.print(line + "\n");
			}
			out.close();
			in.close();
		}
	}

	/**
	 * This service demonstrates how to maintain state across connections by
	 * saving it in instance variables and using synchronized access to those
	 * variables. It maintains a count of how many clients have connected and
	 * tells each client what number it is
	 **/
	public static class UniqueID implements Service {
		public int id = 0;

		public synchronized int nextId() {
			return id++;
		}

		public void serve(InputStream i, OutputStream o) throws IOException {
			PrintWriter out = new PrintWriter(o);
			out.print("You are client #: " + nextId() + "\n");
			out.close();
			i.close();
		}
	}

	/**
	 * This is a non-trivial service. It implements a command-based protocol
	 * that gives password-protected runtime control over the operation of the
	 * server. See the main() method of the Server class to see how this service
	 * is started.
	 * 
	 * The recognized commands are: password: give password; authorization is
	 * required for most commands add: dynamically add a named service on a
	 * specified port remove: dynamically remove the service running on a
	 * specified port max: change the current maximum connection limit. status:
	 * display current services, connections, and connection limit help: display
	 * a help message quit: disconnect
	 * 
	 * This service displays a prompt, and sends all of its output to the user
	 * in capital letters. Only one client is allowed to connect to this service
	 * at a time.
	 **/
	public static class Control implements Service {
		
		static Object object = new Object();
		Server server; // The server we control
		String password; // The password we require
		boolean connected = false; // Whether a client is already connected

		/**
		 * Create a new Control service. It will control the specified Server
		 * object, and will require the specified password for authorization
		 * Note that this Service does not have a no argument constructor, which
		 * means that it cannot be dynamically instantiated and added as the
		 * other, generic services above can be.
		 **/
		public Control(Server server, String password) {
			this.server = server;
			this.password = password;
		}

		/**
		 * This is the serve method that provides the service. It reads a line
		 * the client, and uses java.util.StringTokenizer to parse it into
		 * commands and arguments. It does various things depending on the
		 * command.
		 **/
		public void serve(InputStream i, OutputStream o) throws IOException {
			// Setup the streams
//			BufferedReader in = new BufferedReader(new InputStreamReader(i));
//			PrintWriter out = new PrintWriter(o);
//			String line; // For reading client input lines
//			// Has the user has given the password yet?
//			boolean authorized = false;

			// If there is already a client connected to this service, display
			// a message to this client and close the connection. We use a
			// synchronized block to prevent a race condition.
			synchronized (this) {
				if (connected) {
					i.close();
					o.close();
//					out.print("ONLY ONE CONTROL CONNECTION ALLOWED.\n");
//					out.close();
					return;
				} else
					connected = true;
			}
			serviceDistribution(i, o);
//			// This is the main loop: read a command, parse it, and handle it
//			for (;;) { // infinite loop
//				out.print("> "); // Display a prompt
//				out.flush(); // Make it appear right away
//				line = in.readLine(); // Get the user's input
//				if (line == null)
//					break; // Quit if we get EOF.
//				try {
//					// Use a StringTokenizer to parse the user's command
//					System.out.println(line);
//					
//					StringTokenizer t = new StringTokenizer(line);
//					if (!t.hasMoreTokens())
//						continue; // if input was empty
//					// Get first word of the input and convert to lower case
//					String command = t.nextToken().toLowerCase();
//					// Now compare to each of the possible commands, doing the
//					// appropriate thing for each command
//					if (command.equals("password")) { // Password command
//						String p = t.nextToken(); // Get the next word
//						if (p.equals(this.password)) { // Is it the password?
//							out.print("OK\n"); // Say so
//							authorized = true; // Grant authorization
//						} else
//							out.print("INVALID PASSWORD\n"); // Otherwise fail
//					} else if (command.equals("add")) { // Add Service command
//						// Check whether password has been given
//						if (!authorized)
//							out.print("PASSWORD REQUIRED\n");
//						else {
//							// Get the name of the service and try to
//							// dynamically load and instantiate it.
//							// Exceptions will be handled below
//							String serviceName = t.nextToken();
//							Class serviceClass = Class.forName(serviceName);
//							Service service;
//							try {
//								service = (Service) serviceClass.newInstance();
//							} catch (NoSuchMethodError e) {
//								throw new IllegalArgumentException(
//										"Service must have a "
//												+ "no-argument constructor");
//							}
//							int port = Integer.parseInt(t.nextToken());
//							// If no exceptions occurred, add the service
//							server.addService(service, port);
//							out.print("SERVICE ADDED\n"); // acknowledge
//						}
//					} else if (command.equals("remove")) { // Remove service
//						if (!authorized)
//							out.print("PASSWORD REQUIRED\n");
//						else {
//							int port = Integer.parseInt(t.nextToken());
//							server.removeService(port); // remove the service
//							out.print("SERVICE REMOVED\n"); // acknowledge
//						}
//					} else if (command.equals("max")) { // Set connection limit
//						if (!authorized)
//							out.print("PASSWORD REQUIRED\n");
//						else {
//							int max = Integer.parseInt(t.nextToken());
//							server.setMaxConnections(max);
//							out.print("MAX CONNECTIONS CHANGED\n");
//						}
//					} else if (command.equals("status")) { // Status Display
//						if (!authorized)
//							out.print("PASSWORD REQUIRED\n");
//						else
//							server.displayStatus(out);
//					} else if (command.equals("help")) { // Help command
//						// Display command syntax. Password not required
//						out.print("COMMANDS:\n" + "\tpassword \n" + "\tadd  \n"
//								+ "\tremove \n" + "\tmax \n" + "\tstatus\n"
//								+ "\thelp\n" + "\tquit\n");
//					} else if (command.equals("quit"))
//						break; // Quit command.
//					else
//						out.print("UNRECOGNIZED COMMAND\n"); // Error
//				} catch (Exception e) {
//					// If an exception occurred during the command, print an
//					// error message, then output details of the exception.
//					out.print("ERROR WHILE PARSING OR EXECUTING COMMAND:\n" + e
//							+ "\n");
//				}
//			}
			// Finally, when the loop command loop ends, close the streams
			// and set our connected flag to false so that other clients can
			// now connect.
			
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			connected = false;
			i.close();
			o.close();
		}
		
		protected static synchronized void serviceDistribution(InputStream input, OutputStream output) throws IOException
		{
			String serviceName = "";
			int avalibleLen = 0;
			ProtoBuf[] protoBuf = null;
			long startTime = System.currentTimeMillis();
			while(input.available() == 0)
			{
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				long duration = System.currentTimeMillis() - startTime;
				if(duration > 10000)
				{
					break;
				}
			}
			if(input != null &&  (avalibleLen = input.available()) > 0)
			{
				byte[] data = new byte[avalibleLen];
				input.read(data);
				protoBuf = ProtoBufUtil.toBufArray(data);
			
				if(protoBuf.length > 0)
				{
					serviceName = protoBuf[0].getObjType();
					System.out.println("serviceName: " + serviceName);
				}
			}
			boolean isServiceStatus = false;
			if(IServiceConstants.ACTION_GET_PICTURE.equalsIgnoreCase(serviceName))
			{
				isServiceStatus = ImageSyncService.serve(protoBuf, output);
			}
			else if(IServiceConstants.ACTION_GET_TEXT.equalsIgnoreCase(serviceName))
			{
				isServiceStatus = TextUpdateService.serve(protoBuf, output);
			}
			else if(IServiceConstants.ACTION_UPLOAD_TEXT.equalsIgnoreCase(serviceName))
			{
				isServiceStatus = TextUpLoadService.serve(protoBuf, output);
			}
			if(!isServiceStatus)
			{
				System.out.println("Did not served !");
			}
		}
	}
}
